﻿using System;
using System.Collections.Generic;
using TSU.FAMC.ComputerEngineering.Classes.Enums;
using TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Fundamental;
using System.IO;
using TSU.FAMC.ComputerEngineering.Classes.OrandTrees;
using TSU.FAMC.ComputerEngineering.Classes.OrandTrees.Basis;

namespace TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Functions.Readers
{
    /// <summary>
    /// Reads boolean system by rounding ORNAND-tree.
    /// All leaves of the tree must have input number marks in the Data properties.
    /// </summary>
    public class BooleanSystemReaderOrandTree : BooleanSystemReader
    {
        /// <summary>
        /// Input ORNAD-tree.
        /// </summary>
        public OrandTree Input;

        /// <summary>
        /// List of available circuit's input marks for functions order determination.
        /// </summary>
        public List<string> InputsOrder;

        /// <summary>
        /// Reads boolean system.
        /// </summary>
        public override void Read()
        {
            if (ReferenceEquals(null, System))
            {
                throw new InvalidOperationException("BooleanSystem object required to read data to.");
            }

            if (ReferenceEquals(null, Input))
            {
                throw new InvalidOperationException("Input ORAND-tree required.");
            }

            var functions = new List<BooleanFunction>();

            if (Input.Leaves.Count == 0)
            {
                throw new InvalidDataException("Required leaves of the ORAND-tree specified in Leaves property.");
            }

            // determing boolean dimension
            List<string> inputMarks;
            if (InputsOrder != null && InputsOrder.Count > 0)
            {
                inputMarks = InputsOrder;
            }
            else if (Input.InputsOrder != null && Input.InputsOrder.Count > 0)
            {
                inputMarks = Input.InputsOrder;
            }
            else
            {
                inputMarks = new List<string>();

                // determining input marks
                foreach (var leaf in Input.Leaves)
                {
                    var inputMark = OrandTree.GetInputMark(leaf);
                    if (inputMarks.Contains(inputMark))
                    {
                        continue;
                    }

                    inputMarks.Add(inputMark);
                }
            }
            
            

            var dimension = inputMarks.Count;


            foreach (var top in Input.Tops)
            {
                // creating fictive tree with one top
                
                //var tree = new OrandTree();
                //tree.Tops.Add(top);
                var tree = Input.ExtractTree(top);

                var function = new BooleanFunction();
                var processedCubes = new List<string>();

                // iterating through conjunctions
                foreach (ElementInterface conjunction in tree)
                {
                    var vertices = conjunction;

                    // creating cube for conjunction and reseting it
                    var cube = new Cube(dimension);
                    for (var i = 0; i < dimension; i++)
                    {
                        cube[i] = BooleanValue.Undefined;
                    }

                    var emptyConjunction = false;
                    foreach (var element in vertices)
                    {
                        var vertex = element as Vertex;
                        if (vertex == null)
                        {
                            throw new InvalidDataException("Conjunction must contains only Vertex objects.");
                        }

                        if ((vertex.Type == VertexType.Constant1 && !vertex.IsInverted) ||
                            (vertex.Type == VertexType.Constant0 && vertex.IsInverted))
                        {
                            // skipping constant 1
                            continue;
                        }

                        if ((vertex.Type == VertexType.Constant1 && vertex.IsInverted) ||
                            (vertex.Type == VertexType.Constant0 && !vertex.IsInverted))
                        {
                            // constant 0 makes conjunction empty
                            emptyConjunction = true;
                            break;
                        }

                        if (vertex.Type != VertexType.Input ||
                            vertex.Data as String == "")
                        {
                            throw new InvalidDataException("Leaf vertex of ORAND-tree must have input number in the Data property and have a Type == VertexType.Input");
                        }

                        var inputNumberStr = vertex.Data.ToString();
                        var inputNumber = inputMarks.IndexOf(inputNumberStr);
                        if (inputNumber < 0)
                        {
                            throw new InvalidDataException(string.Format("Unknown input mark: {0}!", inputNumberStr));
                        }

                        if (cube[inputNumber] != BooleanValue.Undefined &&
                            cube[inputNumber] != (vertex.IsInverted ? BooleanValue.False : BooleanValue.True))
                        {
                            // empty conjunction (x*!x)
                            emptyConjunction = true;
                            break;
                        }

                        cube[inputNumber] = vertex.IsInverted ? BooleanValue.False : BooleanValue.True;
                    }

                    if (emptyConjunction)
                    {
                        continue;
                    }

                    // checking whether there are no identical cubes
                    if (processedCubes.Contains(cube.ToString()))
                    {
                        // skipping identical cube
                        continue;
                    }

                    // caching cube
                    processedCubes.Add(cube.ToString());

                    // assigning cube to function
                    function.Domain1.Add(cube);
                }

                functions.Add(function);
            }

            // setting data
            System.Functions = functions;
        }
    }
}
